package org.example.project.common.util;

import org.apache.commons.codec.Charsets;
import org.apache.commons.codec.binary.Base64;
import org.example.project.common.error.enums.ErrorCode;
import org.example.project.common.error.exception.ServiceException;

import javax.crypto.*;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.Charset;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

/**
 * 提供兼容mysql的aes算法
 * <p>
 * MySQL的AES实现给很多人带来了麻烦。这主要是因为MySQL如何处理加密密钥。
 * 加密密钥被分解成16字节的块，MySQL将使来自前一个块中的字节的来自一个块的字节异或。
 * 如果用户提供的密钥长度小于16字节，则密钥基本上用空字节填充，最多可以获得16个字节。这是MySQL的aes_encrypt（）处理的关键
 *
 * @author xiaogm
 * @ClassName AesUtils
 * @Description: (1)加密算法有：AES，DES，DESede(DES3)和RSA 四种
 * (2) 模式有CBC(有向量模式)和ECB(无向量模式)，向量模式可以简单理解为偏移量，使用CBC模式需要定义一个IvParameterSpec对象
 * (3) 填充模式:
 * * NoPadding: 加密内容不足8位用0补足8位, Cipher类不提供补位功能，需自己实现代码给加密内容添加0, 如{65,65,65,0,0,0,0,0}
 * * PKCS5Padding: 加密内容不足8位用余位数补足8位, 如{65,65,65,5,5,5,5,5}或{97,97,97,97,97,97,2,2}; 刚好8位补8位8
 * @date 2019/8/28 17:21
 **/
public class AesUtils {
    private static final String KEY_ALGORITHM = "AES/ECB/PKCS5Padding";
    private static final String AES = "AES";
    private static final Charset UTF8 = Charsets.UTF_8;

    /**
     * @Description: AES加密
     * @Params
     * @Return
     * @CreateBy: xiaogm
     * @CreateTime: 2019/9/3 9:41
     */
    public static byte[] encrypt(String data, String key) {
        try {
            return encrypt(data.getBytes(UTF8), generateMySQLAESKey(key), Cipher.ENCRYPT_MODE);
        } catch (NoSuchPaddingException | NoSuchAlgorithmException | BadPaddingException | IllegalBlockSizeException | InvalidKeyException e) {
            throw new ServiceException(ErrorCode.B0001, e);
        }
    }

    /**
     * @Description: AES解密
     * @Params
     * @Return
     * @CreateBy: xiaogm
     * @CreateTime: 2019/9/3 9:41
     */
    public static String decrypt(byte[] data, String key) {
        try {
            return new String(encrypt(data, generateMySQLAESKey(key), Cipher.DECRYPT_MODE), UTF8);
        } catch (NoSuchPaddingException | NoSuchAlgorithmException | BadPaddingException | IllegalBlockSizeException | InvalidKeyException e) {
            throw new ServiceException(ErrorCode.B0001, e);
        }
    }

    /**
     * mysql 使用ascii字节，对于key是字母数字等等各种字符集是一样的。如果是中文，这里使用默认的utf8，因为网络传输的字符串是utf8编码
     *
     * @param key
     * @return
     */
    public static SecretKeySpec generateMySQLAESKey(final String key) {
        return generateMySQLAESKey(key, UTF8);
    }

    /**
     * generateMySQLAESKey
     *
     * @param key 密钥
     * @return
     */
    public static SecretKeySpec generateMySQLAESKey(final String key, Charset charset) {
        final byte[] finalKey = new byte[16];
        int i = 0;
        for (byte b : key.getBytes(charset)) {
            finalKey[i++ % 16] ^= b;
        }
        return new SecretKeySpec(finalKey, AES);
    }

    static byte[] encrypt(byte[] bytesData, SecretKey secretKey, int mode) throws NoSuchPaddingException, NoSuchAlgorithmException, BadPaddingException, IllegalBlockSizeException, InvalidKeyException {
        Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
        cipher.init(mode, secretKey);
        return cipher.doFinal(bytesData);
    }

    /**
     * base64使用ascii加密
     *
     * @param data 待加密数据
     * @param key  对称(chèn)密钥
     * @return 加密结果
     */
    public static String base64AesEncrypt(String data, String key) {
        return Base64.encodeBase64String(encrypt(data, key));
    }

    /**
     * base64使用ascii解密
     *
     * @param data 待解密数据
     * @param key  对称(chèn)密钥
     * @return 解密结果
     */
    public static String base64AesDecrypt(String data, String key) {
        return decrypt(Base64.decodeBase64(data), key);
    }
}
